package com.huzz.util.tree;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.huzz.util.entityutil.ModelUtil;

import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.List;

/**
 * 构建树型结构
 *
 * @author Huzz
 * @create 2022-07-25 18:36
 */
public class TreeBuilder<T> {
    private static final String SET_METHOD_PREFIX = "set";
    private static final String GET_METHOD_PREFIX = "get";

    private static final String DEFAULT_ID_NAME = "id";
    private static final String DEFAULT_PARENT_ID_NAME = "parentId";
    private static final String DEFAULT_CHILDREN_NAME = "children";

    /**
     * 构建树结构
     *
     * @param rootId   根节点id
     * @param dataList 所有节点数据, 包含id|父级id|孩子 成员属性.
     * @return
     * @throws Exception
     */
    public static <T> List<T> builder(String rootId, List<T> dataList) throws Exception {
        List<T> tree = new ArrayList<>();
        for (T data : dataList) {
            String parentId = data.getClass().getMethod(generateGetMethodName(DEFAULT_PARENT_ID_NAME)).invoke(data).toString();

            if (rootId.equals(parentId)) {
                // 是根节点的话, 递归查找以该节点id作为parentId的孩子
                List<T> children = builder(data.getClass().getMethod(generateGetMethodName(DEFAULT_ID_NAME)).invoke(data).toString(), dataList);
                // 设置孩子
                data.getClass().getMethod(generateSetMethodName(DEFAULT_CHILDREN_NAME), List.class).invoke(data, children);
                // 保存当前节点
                tree.add(data);
            }
        }

        return tree;
    }

    /**
     * 对IPage对象列表生成树型结构
     *
     * @param baseMapper
     * @param page
     * @param wrapper wrapper只对最外层生效
     * @param type
     * @return
     * @param <ET>
     * @param <VO>
     */
    public static <ET,VO> IPage<VO> builderIPageTree(BaseMapper<ET> baseMapper, Page<ET> page, QueryWrapper<ET> wrapper,  Class<VO> type) {
        wrapper.eq("parent_id", 0);
        IPage<ET> iPage = baseMapper.selectPage(page, wrapper);
        // 父级列表
        IPage<VO> viPage = ModelUtil.transToSimilarPage(iPage, type);

        List<ET> cList = baseMapper.selectList(Wrappers.<ET>query().ne("parent_id", 0));
        // 所有子集
        List<VO> vList = ModelUtil.transToSimilarList(cList, type);
        // 生成树
        viPage.getRecords().forEach(item -> {
            try {
                String rootId = item.getClass().getMethod(generateGetMethodName(DEFAULT_ID_NAME)).invoke(item).toString();
                List<VO> children = builder(rootId, vList);
                item.getClass().getMethod(generateSetMethodName(DEFAULT_CHILDREN_NAME), List.class).invoke(item, children);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        });
        return viPage;
    }

    /**
     * 生成List对象的树型结构
     * vo对象中必须有一个List<vo>类型的children字段
     * @param mapper
     * @param wrapper wrapper只对最外层生效
     * @param vo
     * @return
     * @param <ET>
     * @param <VO>
     */
    public static<ET,VO> List<VO> builderTree(BaseMapper<ET> mapper, QueryWrapper<ET> wrapper, Class<VO> vo) {
        wrapper.eq("parent_id", 0);
        List<VO> rlist = ModelUtil.transToSimilarList(mapper.selectList(wrapper), vo);
        List<VO> clist = ModelUtil.transToSimilarList(mapper.selectList(Wrappers.<ET>query().ne("parent_id", 0)), vo);

        List<VO> builder = new ArrayList<>();
        rlist.forEach(item -> {

            try {
                String id = item.getClass().getMethod(generateGetMethodName("id")).invoke(item).toString();
                List<VO> children = builder(id, clist);
                item.getClass().getMethod(generateSetMethodName("children"), List.class).invoke(item, children);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
            builder.add(item);
        });
        return builder;
    }

    /**
     * 获取所有父级数据集合
     *
     * @param parentId          当前父级ID
     * @param dataList          集合
     * @return 只有父级组成的list
     * @throws Exception
     */
    public static<ET> List<ET> builderAllParentList(String parentId, List<ET> dataList) throws Exception {
        List<ET> result = new ArrayList();
        for (ET data : dataList) {
            String id = data.getClass().getMethod(generateGetMethodName("id")).invoke(data).toString();
            if (parentId.equals(id)) {
                result.add(data);
                result.addAll(builderAllParentList(data.getClass().getMethod(generateGetMethodName("parentId")).invoke(data).toString(), dataList));
            }
        }
        return result;
    }

    private static<ET> StringBuffer builderCurrentPath(String parentId, List<ET> dataList, String pathFiledName) throws Exception {
        StringBuffer path = new StringBuffer();
        for (ET data : dataList) {
            String id = data.getClass().getMethod(generateGetMethodName("id")).invoke(data).toString();
            if (parentId.equals(id)) {
                path.append("/" + data.getClass().getMethod(generateGetMethodName(pathFiledName)).invoke(data).toString());
                String p = builderCurrentPath(data.getClass().getMethod(generateGetMethodName("parentId")).invoke(data).toString(), dataList, pathFiledName).toString();
                path.insert(0, p);
            }
        }
        return path;
    }

    /**
     * 获取当前节点所在路径
     *
     * @param currentData   当前节点
     * @param dataList      集合
     * @param pathFiledName 要设置为路径名称的字段名称
     * @return 例: /根目录1/子目录2/子目录3/子目录4 (当前: 子目录4)
     * @throws Exception
     */
    public static<ET> String builderCurrentPath(ET currentData, List<ET> dataList, String pathFiledName) throws Exception {
        String parentId = currentData.getClass().getMethod(generateGetMethodName("parentId")).invoke(currentData).toString();
        String currentNode = currentData.getClass().getMethod(generateGetMethodName(pathFiledName)).invoke(currentData).toString();

        StringBuffer path = builderCurrentPath(parentId, dataList, pathFiledName);

        return path + "/" + currentNode;
    }

    /**
     * 收集它下面的所有非树型列表 (建议)
     * @param rootId
     * @param mapper
     * @return
     * @param <T>
     * @param <ET>
     */
    public static<T, ET> List<ET> builderAllChildren(T rootId, BaseMapper<ET> mapper) {
        List<ET> result = new ArrayList<>();

        List<ET> list = mapper.selectList(Wrappers.<ET>query().eq("parent_id", rootId));

        list.forEach(item -> {
            result.add(item);

            try {
                T rId = (T) item.getClass().getMethod(generateGetMethodName("id")).invoke(item);
                result.addAll(builderAllChildren(rId, mapper));
            } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
                throw new RuntimeException(e);
            }

        });
        return result;
    }

    /**
     * 收集它下面的所有非树id列表 (建议)
     * @param rootId
     * @param mapper
     * @return
     * @param <T>
     * @param <ET>
     */
    public static<T, ET> List<T> builderAllChildrenIds(T rootId, BaseMapper<ET> mapper) {
        List<T> result = new ArrayList<>();

        List<ET> list = mapper.selectList(Wrappers.<ET>query().eq("parent_id", rootId));

        list.forEach(item -> {
            try {
                T rId = (T) item.getClass().getMethod(generateGetMethodName("id")).invoke(item);

                result.add(rId);
                result.addAll(builderAllChildrenIds(rId, mapper));
            } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
                throw new RuntimeException(e);
            }

        });
        return result;
    }

    private static String generateGetMethodName(String filedName) {
        return GET_METHOD_PREFIX + filedName.substring(0, 1).toUpperCase() + filedName.substring(1);
    }

    private static String generateSetMethodName(String filedName) {
        return SET_METHOD_PREFIX + filedName.substring(0, 1).toUpperCase() + filedName.substring(1);
    }

}
